﻿using System;
using System.Collections.Generic;
using gbL.NetBoard.Broadcast;
using System.Diagnostics;

namespace gbL.NetBoard.Helpers
{
    class ReceiverAdapter : IDisposable
    {
        private readonly IBlackboard _blackboard;

        private readonly List<Action<ChangedEventArgs>> _addedActions;

        private readonly List<Action<ChangedEventArgs>> _removedActions;

        public bool Enabled { get; set; }

        public ListenerMessageBehaviours MessageBehaviour { get; set; }


        public ReceiverAdapter(IBlackboard blackboard)
        {
            _blackboard = blackboard;
            _addedActions = new List<Action<ChangedEventArgs>>();
            _removedActions = new List<Action<ChangedEventArgs>>();
            MessageBehaviour = ListenerMessageBehaviours.LeaveMessage;
            RegisterForBroadcasts();
        }

        private void RegisterForBroadcasts()
        {
            _blackboard.Broadcaster.Added += new EventHandler<gbL.NetBoard.Broadcast.ChangedEventArgs>(Broadcaster_Added);
            _blackboard.Broadcaster.Removed += new EventHandler<gbL.NetBoard.Broadcast.ChangedEventArgs>(Broadcaster_Removed);
        }

        private void DeregisterForBroadcasts()
        {
            _blackboard.Broadcaster.Added -= new EventHandler<gbL.NetBoard.Broadcast.ChangedEventArgs>(Broadcaster_Added);
            _blackboard.Broadcaster.Removed -= new EventHandler<gbL.NetBoard.Broadcast.ChangedEventArgs>(Broadcaster_Removed);
        }

        void Broadcaster_Removed(object sender, gbL.NetBoard.Broadcast.ChangedEventArgs e)
        {
            HandleEventForEntry(e, _removedActions);
        }

        void Broadcaster_Added(object sender, gbL.NetBoard.Broadcast.ChangedEventArgs e)
        {
            HandleEventForEntry(e, _addedActions);
        }

        private void HandleEventForEntry(gbL.NetBoard.Broadcast.ChangedEventArgs e, List<Action<ChangedEventArgs>> actions)
        {
            if (!Enabled) return;
            bool handleMessage = true;
            if (MessageBehaviour != ListenerMessageBehaviours.LeaveMessage)
            {
                var taken = _blackboard.TakeEntry(e.Entry);
                if (MessageBehaviour == ListenerMessageBehaviours.MustTake && taken == null)
                    handleMessage = false;
            }
            if (handleMessage)
            {
                foreach (var action in actions)
                    action(e);
            }
        }

        public void Accept<T>(Action<T> messageAddedAction, Action<T> messageRemovedAction)
        {
            Type targetType = typeof(T);

            _blackboard.Broadcaster.FilterAddedByType(this, targetType);
            _blackboard.Broadcaster.FilterRemovedByType(this, targetType);

            AddTypeFilteredAction<T>(targetType, _addedActions, messageAddedAction);
            AddTypeFilteredAction<T>(targetType, _removedActions, messageRemovedAction);
        }

        private static void AddTypeFilteredAction<T>(Type targetType, List<Action<ChangedEventArgs>> actions, Action<T> action)
        {
            actions.Add(e =>
            {
                if (targetType.IsAssignableFrom(e.Entry.MessageType))
                    action((T)e.Entry.Message);
            });
        }

        public void AcceptEntry<T>(Action<IBlackboardEntry> messageAddedAction, Action<IBlackboardEntry> messageRemovedAction)
        {
            Type targetType = typeof(T);

            _blackboard.Broadcaster.FilterAddedByType(this, targetType);
            _blackboard.Broadcaster.FilterRemovedByType(this, targetType);

            AddTypeFilteredAction(targetType, _addedActions, messageAddedAction);
            AddTypeFilteredAction(targetType, _removedActions, messageRemovedAction);
        }

        private static void AddTypeFilteredAction(Type targetType, List<Action<ChangedEventArgs>> actions, Action<IBlackboardEntry> action)
        {
            actions.Add(e =>
            {
                if (e.Entry.MessageType.IsAssignableFrom(targetType))
                    action(e.Entry);
            });
        }

        public void Dispose()
        {
            DeregisterForBroadcasts();
        }
    }
}
